﻿namespace FFF.Test.Application.PublicService.AppService.Wx.Pay
{
    using System;
    using System.Net.Security;
    using System.Net;
    using System.Security.Cryptography.X509Certificates;
    using System.IO;
    using System.Xml.Linq;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using Abp.Application.Services;
    using Abp.Dependency;
    using FFF.Test.Application.AppServices;
    using FFF.Test.Application.PublicService.AppService.Wx.Pay.Dto;
    using Senparc.Weixin;
    using Senparc.Weixin.TenPay;
    using Senparc.Weixin.TenPay.V3;
    using Microsoft.AspNetCore.Mvc;
    using Abp.EntityFrameworkCore;
    using FFF.Test.DataAccess.Framework;
    using FFF.Test.Util.Consts;
    using Microsoft.AspNetCore.Http;
    using FFF.Test.Entity.WX.Dto;
    using FFF.Test.Application.Repositories.WX;
    using Microsoft.AspNetCore.Authorization;
    using Abp.ObjectMapping;
    using Abp.UI;

    /// <summary>
    /// 微信支付接口（p3版本）
    /// </summary>
    public interface ITenPay3AppService : ITransientDependency
    {
        /// <summary>
        /// 支付通知接口
        /// </summary>
        /// <returns>通知结果</returns>
        Task<ContentResult> PayNotifyUrl();

        /// <summary>
        /// 下单接口
        /// </summary>
        /// <param name="inDto">支付输入dto</param>
        /// <returns>输出dto</returns>
        Task<TenPayV3OrderOutDto> Order(TenPayV3OrderInDto inDto);

        /// <summary>
        /// 支付失败接口
        /// </summary>
        /// <param name="inDto">支失败付输入dto</param>
        /// <returns>输出dto</returns>
        Task OrderFail(OrderFailDto inDto);

        /// <summary>
        /// 退款接口
        /// </summary>
        /// <param name="inDto">支失败付输入dto</param>
        /// <returns>输出dto</returns>
        Task Refund(RefundDto inDto);

        ///// <summary>
        ///// 退款接口
        ///// </summary>
        ///// <returns>输出dto</returns>
        //Task<ContentResult> RefundNotifyUrl();

        ///// <summary>
        ///// 查询订单状态接口
        ///// </summary>
        ///// <param name="inDto">订单查询输入dto</param>
        ///// <returns>输出dto表示是否成功</returns>
        //Task<OrderQueryOutDto> OrderQuery(OrderQueryInDto inDto);
    }

    /// <summary>
    /// 微信支付接口（p3版本）
    /// </summary>
    [YdmApiVersionAttribute(Version = "My")]
    public class TenPay3AppService : ITenPay3AppService
    {
        /// <summary>
        ///  依赖注入容器
        /// </summary>
        private readonly IIocResolver _iocResolver;

        private readonly IDbContextProvider<MyDbContext> _dbContextProvider;

        private readonly IWXPayOrderRepository _wXPayOrderRepository;

        /// <summary>
        /// Initializes a new instance of the <see cref="TenPay3AppService"/> class.
        /// 构造方法
        /// </summary>
        /// <param name="iocResolver">注入容器</param>
        /// <param name="dbContextProvider">dbcontext对象</param>
        /// <param name="wXPayOrderRepository">微信仓储层</param>
        public TenPay3AppService(
            IIocResolver iocResolver,
            IDbContextProvider<MyDbContext> dbContextProvider,
            IWXPayOrderRepository wXPayOrderRepository

            )
        {
            this._iocResolver = iocResolver;
            this._dbContextProvider = dbContextProvider;
            this._wXPayOrderRepository = wXPayOrderRepository;
        }

        /// <summary>
        /// 下单接口
        /// </summary>
        /// <param name="inDto">支付输入dto</param>
        /// <returns>输出dto</returns>
        [HttpPost]
        [Authorize(Roles = "admin")]
        public async Task<TenPayV3OrderOutDto> Order(TenPayV3OrderInDto inDto)
        {
            var db = this._dbContextProvider.GetDbContext();
            TenPayV3Info tenPayV3Info = TenPayV3InfoCollection.Data[Config.SenparcWeixinSetting.TenPayV3_MchId + "_"];

            ProductDto productDto = inDto.ProductDto;
            string sp_billno = db.GetFlowNumber("WX_PayOrder");
            string timeStamp = TenPayV3Util.GetTimestamp();
            string nonceStr = TenPayV3Util.GetNoncestr();

            string body = productDto.ProductName; // 产品名称

            string openId = inDto.OpenId;

            //  string subId = await db.ViewGZH09.Where(x => x.StoreID == inDto.StoreId).Select(x => x.MerchartNO).FirstOrDefaultAsync();
            string subAppId = AppConfigConsts.SenparcWeixinSetting.WxOpenAppId;
            var httpContext = this._iocResolver.Resolve<IHttpContextAccessor>();
            var spbillCreateIp = "127.0.0.1";
            if (httpContext.HttpContext != null)
            {
                spbillCreateIp = httpContext.HttpContext.UserHostAddress()?.ToString();
            }

            string notifyUrl = AppConfigConsts.AppDomain + tenPayV3Info.TenPayV3_WxOpenNotify;
            int price = 0; // 单位：分
            string subId = null;

            //TenPayV3UnifiedorderRequestData xmlDataInfo =
            //    new TenPayV3UnifiedorderRequestData(
            //        tenPayV3Info.AppId,
            //        tenPayV3Info.MchId,
            //        body,
            //        sp_billno,
            //        price,
            //        null,
            //        notifyUrl,
            //        TenPayV3Type.JSAPI,
            //        openId,
            //        tenPayV3Info.Key,
            //        nonceStr);

            // SubId:1391611202
            var xmlDataInfo = new TenPayV3UnifiedorderRequestData(
                   appId: tenPayV3Info.AppId,
                   mchId: tenPayV3Info.MchId,
                   subappid: subAppId,
                   submchid: subId,
                   body: body,
                   outTradeNo: sp_billno,
                   totalFee: price,
                   spbillCreateIp: spbillCreateIp,
                   notifyUrl: notifyUrl,
                   tradeType: TenPayV3Type.JSAPI,
                   openid: string.Empty,
                   subOpenid: inDto.OpenId,
                   key: tenPayV3Info.Key,
                   nonceStr: nonceStr
                );

            UnifiedorderResult result = await TenPayV3.UnifiedorderAsync(xmlDataInfo); //调用统一订单接口
                                                                                       // JsSdkUiPackage jsPackage = new JsSdkUiPackage(TenPayV3Info.AppId, timeStamp, nonceStr,);
            string package = string.Format("prepay_id={0}", result.prepay_id);
            TenPayV3OrderOutDto outDto = new TenPayV3OrderOutDto()
            {
                AppID = subAppId,
                TimeStamp = timeStamp,
                NonceStr = nonceStr,
                Package = package,
                PaySign = TenPayV3.GetJsPaySign(subAppId, timeStamp, nonceStr, package, tenPayV3Info.Key),
                OutTradNo = sp_billno
            };

            // 插入订单表
            WXPayOrderCreateInputDto inputDto = new WXPayOrderCreateInputDto()
            {
                AppId = tenPayV3Info.AppId,
                MchId = tenPayV3Info.MchId,
                DeviceInfo = string.Empty,
                Body = productDto.ProductName,
                FeeType = "CNY",
                TotalFee = price,
                SpbillCreateIp = spbillCreateIp,
                TimeStart = DateTime.Now.ToString("yyyyMMddHHmmss"),
                TimeExpire = DateTime.Now.AddMinutes(20).ToString("yyyyMMddHHmmss"),
                GoodsTag = string.Empty,
                TradeType = "1",
                Detail = inDto.ProductDto.ProductId,
                LimitPay = string.Empty,
                OpenId = openId,
                PrepayId = string.Empty,
                CodeUrl = string.Empty,
                TransactionId = string.Empty,
                SubMchId = subId,
                SubAppId = subAppId,
                SubOpenid = openId,
                OutTradeNo = sp_billno,
                NotifyUrl = notifyUrl,
                PayOrderState = Entity.WX.WXPayOrderPayOrderStateEnum.未支付
            };
            await this._wXPayOrderRepository.CreateByDtoAsync(inputDto);

            return outDto;
        }

        /// <summary>
        /// 支付失败接口
        /// </summary>
        /// <param name="inDto">支失败付输入dto</param>
        /// <returns>输出dto</returns>
        [HttpPost]
        [Authorize(Roles = "admin")]
        public async Task OrderFail(OrderFailDto inDto)
        {
            var db = this._dbContextProvider.GetDbContext();
            var wxPayEntity = await this._wXPayOrderRepository.FirstOrDefaultAsync(x => x.OutTradeNo == inDto.OutTradNo);
        }

        /// <summary>
        /// JS-SDK支付回调地址（在统一下单接口中设置notify_url）
        /// </summary>
        /// <returns>通知结果</returns>
        [HttpPost]
        public async Task<ContentResult> PayNotifyUrl()
        {
            var db = this._dbContextProvider.GetDbContext();
            TenPayV3Info tenPayV3Info = TenPayV3InfoCollection.Data[Config.SenparcWeixinSetting.TenPayV3_MchId + "_"];
            var httpContext = this._iocResolver.Resolve<IHttpContextAccessor>().HttpContext;
            ResponseHandler resHandler = new ResponseHandler(httpContext);
            Senparc.CO2NET.Trace.SenparcTrace.SendCustomLog("跟踪PayNotifyUrl信息", resHandler.ParseXML());
            string return_code = resHandler.GetParameter("return_code");
            string return_msg = resHandler.GetParameter("return_msg");
            string tradNo = resHandler.GetParameter("out_trade_no");

            var wxPayEntity = await this._wXPayOrderRepository.FirstOrDefaultAsync(x => x.OutTradeNo == tradNo);

            // wxPayEntity.PaidOn = DateTime.Now;

            // wxPayEntity.TimeEnd = DateTime.Now.ToString("yyyyMMddHHmmss");
            wxPayEntity.Attach = resHandler.GetParameter("attach");

            //wxPayEntity.BankType = resHandler.GetParameter("bank_type");
            //wxPayEntity.CashFee = resHandler.GetParameter("cash_fee").ToDefault(0);
            wxPayEntity.DeviceInfo = resHandler.GetParameter("device_info");
            wxPayEntity.FeeType = resHandler.GetParameter("fee_type");
            wxPayEntity.IsSubscribe = resHandler.GetParameter("is_subscribe");
            wxPayEntity.NonceStr = resHandler.GetParameter("nonce_str");
            wxPayEntity.TransactionId = resHandler.GetParameter("transaction_id");
            wxPayEntity.Sign = resHandler.GetParameter("sign");

            //wxPayEntity.TimeEnd = resHandler.GetParameter("time_end");
            //wxPayEntity.PaidByOpenID = resHandler.GetParameter("openid");

            var mapper = this._iocResolver.Resolve<IObjectMapper>();
            var updateDto = mapper.Map<WXPayOrderUpdateInputDto>(wxPayEntity);
            await this._wXPayOrderRepository.UpdateByDtoAsync(updateDto);

            string res = null;

            resHandler.SetKey(tenPayV3Info.Key);

            //验证请求是否从微信发过来（安全）
            if (resHandler.IsTenpaySign() && return_code.ToUpper() == "SUCCESS")
            {
                res = "success";
                wxPayEntity.PayOrderState = Entity.WX.WXPayOrderPayOrderStateEnum.支付成功;
            }
            else
            {
                res = "wrong"; // 错误的订单处理
                wxPayEntity.PayOrderError = return_msg;
            }

            string xml = string.Format(
                @"<xml>
                <return_code><![CDATA[{0}]]></return_code>
                <return_msg><![CDATA[{1}]]></return_msg>
                </xml>",
            return_code,
            return_msg);
            ContentResult content = new ContentResult()
            {
                Content = xml,
                ContentType = "text/xml"
            };

            return content;
        }

        /// <summary>
        /// 退款接口
        /// </summary>
        /// <param name="inDto">退款接口Dto</param>
        /// <returns>退款接口返回</returns>
        [HttpPost]
        [Authorize(Roles = "admin")]
        public async Task Refund(RefundDto inDto)
        {
            var db = this._dbContextProvider.GetDbContext();
            TenPayV3Info tenPayV3Info = TenPayV3InfoCollection.Data[Config.SenparcWeixinSetting.TenPayV3_MchId + "_"];
            var wxPayEntity = await this._wXPayOrderRepository.FirstOrDefaultAsync(x => x.OutTradeNo == inDto.OutTradNo);
            string nonceStr = TenPayV3Util.GetNoncestr();
            string outTradeNo = inDto.OutTradNo;
            string outRefundNo = "OutRefunNo-" + DateTime.Now.Ticks;
            int totalFee = int.Parse(wxPayEntity.TotalFee.ToString());
            int refundFee = totalFee;
            string opUserId = tenPayV3Info.MchId;
            string notifyUrl = AppConfigConsts.AppDomain + "/api/services/app/TenpayV3/RefundNotifyUrl";

            #region 原始方法

            RequestHandler packageReqHandler = new RequestHandler(null);

            packageReqHandler.SetParameter("appid", tenPayV3Info.AppId);		 //公众账号ID
            packageReqHandler.SetParameter("mch_id", tenPayV3Info.MchId);        //商户号
            packageReqHandler.SetParameter("sub_appid", wxPayEntity.SubAppId);		 //公众账号ID
            packageReqHandler.SetParameter("sub_mch_id", wxPayEntity.SubMchId);	     //商户号
            packageReqHandler.SetParameter("out_trade_no", outTradeNo); //填入商家订单号
            packageReqHandler.SetParameter("out_refund_no", outRefundNo);                //填入退款订单号
            packageReqHandler.SetParameter("total_fee", wxPayEntity.TotalFee.ToString());                    //填入总金额
            packageReqHandler.SetParameter("refund_fee", refundFee.ToString());                //填入退款金额
            packageReqHandler.SetParameter("op_user_id", tenPayV3Info.MchId);   //操作员Id，默认就是商户号
            packageReqHandler.SetParameter("nonce_str", nonceStr);              //随机字符串
            string sign = packageReqHandler.CreateMd5Sign("key", tenPayV3Info.Key);
            packageReqHandler.SetParameter("sign", sign);	                    //签名
            //退款需要post的数据
            string data = packageReqHandler.ParseXML();

            //退款接口地址
            string url = "https://api.mch.weixin.qq.com/secapi/pay/refund";

            //本地或者服务器的证书位置（证书在微信支付申请成功发来的通知邮件中）

            string cert = @"D:\cert\apiclient_cert.p12";

            //私钥（在安装证书时设置）
            string password = tenPayV3Info.MchId;

            //  ServicePointManager.ServerCertificateValidationCallback = new RemoteCertificateValidationCallback(CheckValidationResult);

            //调用证书
            X509Certificate2 cer = new X509Certificate2(cert, password, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.MachineKeySet);

            #region 发起post请求
            HttpWebRequest webrequest = (HttpWebRequest)HttpWebRequest.Create(url);
            webrequest.ClientCertificates.Add(cer);
            webrequest.Method = "post";

            byte[] postdatabyte = Encoding.UTF8.GetBytes(data);
            webrequest.ContentLength = postdatabyte.Length;
            Stream stream;
            stream = webrequest.GetRequestStream();
            stream.Write(postdatabyte, 0, postdatabyte.Length);
            stream.Close();

            HttpWebResponse httpWebResponse = (HttpWebResponse)webrequest.GetResponse();
            StreamReader streamReader = new StreamReader(httpWebResponse.GetResponseStream());
            string responseContent = streamReader.ReadToEnd();
            #endregion

            var res = XDocument.Parse(responseContent);
            string return_code = res.Element("xml").Element("return_code").Value;
            string return_msg = res.Element("xml").Element("return_msg").Value;
            if (return_code != "SUCCESS")
            {
                throw new UserFriendlyException($"退款失败错误信息:{return_msg}");
            }
            else
            {
                wxPayEntity.PayOrderState = Entity.WX.WXPayOrderPayOrderStateEnum.退款;
            }

            var mapper = this._iocResolver.Resolve<IObjectMapper>();
            var updateDto = mapper.Map<WXPayOrderUpdateInputDto>(wxPayEntity);
            await this._wXPayOrderRepository.UpdateByDtoAsync(updateDto);

            #endregion

            //var dataInfo = new TenPayV3RefundRequestData(
            //    appId: tenPayV3Info.AppId,
            //    mchId: tenPayV3Info.MchId,
            //    subAppId: pay01Entity.SubAppID,
            //    subMchId: pay01Entity.SubMchID,
            //    key: tenPayV3Info.Key,
            //    deviceInfo: null,
            //    nonceStr: nonceStr,
            //    transactionId: null,
            //    outTradeNo: outTradeNo,
            //    outRefundNo: outRefundNo,
            //    totalFee: totalFee,
            //    refundFee: refundFee,
            //    opUserId: opUserId,
            //    refundAccount: null,
            //    refundDescription: null,
            //    notifyUrl: notifyUrl);
            //var result = TenPayV3.Refund(dataInfo);

        }

        private static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
        {
            if (errors == SslPolicyErrors.None)
            {
                return true;
            }

            return false;
        }
    }

}
